package cwb
import agent.Action
import agent.Agent
import scala.util.parsing.combinator.RegexParsers
import scala.util.Random

sealed trait SimCommand extends CommonCommand {
  def isInt(input: String): Boolean = {
      if (!input.matches("\\d+")) {
        println("Error: expects an integer parameter!")
        false
      } else true
  }
}

object SimSim extends SimCommand {
  val name = "sim"
      def apply(input: String) {
    cwb.commandsPool = SimCommands

        val agent = Agent.readAgent(input)
        Simulation.state = agent
        Simulation.breaks = List()
        Simulation.newState = true
        Simulation.hist = List()
        Simulation.showMenu()
  }
}

object SimBindId extends SimCommand {
  val name = "bind"
      def apply(input: String) {
    val strList = input.trim().split(" ")
        strList.length match {
        case 0 => println("Bind expects a variable")
        case 1 => {
          Agent.bindVarsToAgents(List((Symbol(strList(0)), Simulation.state)))
        }
        case _ => println(" *** [34M]Extra input *** ")
    }
  }
}

object SimRandom extends SimCommand {
  val name = "random"
      def apply(input: String) {
    val param = input.trim
        if (isInt(param)) {
          val n = param.toInt
              if (n > 0) {
                Simulation.random(n,n,Simulation.state.transitions())
                Simulation.showMenu()
              } else {
                println("** Must be a positive number of steps. **")
              }
        }
  }  
}

/**
 * Executes the given transition index.
 */
object SimChoose extends SimCommand {
  // (i: Int)
  val name = "choose"
      def apply(input: String) {
    val param = input.trim
        if (isInt(param)) {
          val n = param.toInt
              Simulation.execTrans((n, Simulation.state.transitions()))
              Simulation.showMenu()
        }

  }  
}


object SimBackTrack extends SimCommand {
  val name = "return"


    def apply(input: String) {
    val param = input.trim
        if (isInt(param)) {
          val hist = Simulation.hist
              val n = param.toInt
              if (n == hist.length) {
                println("** This is already the current state. **")
              } else if (n > hist.length) {
                println("** History state " + n + " does not exist. **")
              } else {
                // Now we know we have sane data, perform history reversion!
                def backup(klst: (Int, List[(Agent, Action)])): Unit = {
                  klst match {
                  case (k, List()) =>   println("** History state " +  n + " does not exist. **")
                  case (1, actGent::t) => {
                    Simulation.hist = t
                        Simulation.state = actGent._1
                        Simulation.showMenu()
                  }
                  case (k, curr::t) => backup(k-1, t)
                  }

                }
                backup(hist.length - n, hist)
              }
        }
  }
}

/**
 * Sets a breakpoint action.
 */
object SimBreak extends SimCommand {
  //(actList: List[Action])
  val name = "break"
      def apply(input: String) {
    val act = Agent.readAct(input.trim())
        Simulation.check(act)
        Simulation.breaks =  (Simulation.breaks ++ List(act)).removeDuplicates.sortWith(
            (a1, a2) => a1.geAction(a2))
  }  
}
/**
 * Deletes a breakpoint action
 */
object SimDelBreak extends SimCommand {
  // (actList: List[Action])
  val name = "db"
      def apply(input: String) {
    val act = Agent.readAct(input.trim())
        Simulation.breaks = Simulation.breaks.remove(a => a == act)
  }  
}

/**
 * Lists the currently set break points.
 */
object SimListBreaks extends SimCommand {
  val name = "lb"
      def apply(input: String) {
    if (Simulation.breaks == Nil) {
      println("There are no break points set.")
    } else {
      val breakListString = for(act<-Simulation.breaks) yield act.mkstr
          println("Breakpoints: " + breakListString.mkString(", "))
    }

  }  
}
/**
 * Exits the simulation mode and returns to the standard CWB mode.
 */
object SimHalt extends SimCommand {
  val name = "quit"
      def apply(input: String) {
    cwb.commandsPool = CWBCommands

  }  
}

/**
 * Displays help information for  all simulation commands. 
 */
object SimHelp extends SimCommand {
  val name = "help"
      def apply(input: String) {
    SimCommands.showAvailableCommands()
  }  
}
object SimMenu extends SimCommand {
  val name = "menu"
      def apply(input: String) {
    Simulation.menu(Simulation.state.transitions(), 1)
  }  
}
object SimHistory extends SimCommand {
  val name = "history"

      def histString(agAct: (Agent, Action)) = {
    agAct match {
    case (agent, action) => agent.agentString + " --- " + action.mkstr + " --->"
    }
  }
  def apply(input: String) {
    val hist = Simulation.hist
        def pre(i: Int) = "    " + i + ": "
        val maxId = hist.size -1
        for (i <- 0 until hist.length) {
          println(pre(i) + histString(hist(maxId - i)))
        }
    println(pre(hist.length) + Simulation.state.agentString)
  }  
}




object Simulation {
  // Register the simulation commands.
  SimCommands.registerCommands(List(SimSim, SimBindId, SimRandom, SimChoose, SimHelp,
      SimBackTrack, SimBreak, SimDelBreak, SimListBreaks, SimHalt, SimMenu, SimHistory))


      def showMenu() = menu(state.transitions(), 1)


      /************************ Now as we were: ***********************************/
      var hist =  List[(Agent, Action)]()      /* History */
      var state  = Agent.makeAgentFromNextSteps(List())   /* Current state */
      var breaks =  List[Action]()        /* Breakpoints */

      var continue  = true  /* Will halt ? */
      var newState  = false  /* Has executed ? */


      def check(a: Action) = {
    if (a.observable()){
      a
    } else {
      throw new CWBParseException("Invalid use of " + a.mkstr())
    }
  }

  /**
   * Execures n transitions or m < n if a break point action or deadlock is found after
   * m transitions
   * @param gen0 (total number of transitions to be made, remaining transitions, available
   * transtions from 
   *  Execute n random transitions */
  def random(gen0: (Int, Int, List[(Action, Agent)])): Unit = {
    gen0 match {
    case ((orign,n,transes)) => {
      val rand = new Random
          if (transes == Nil) {
            println("** Simulation terminated: Deadlock. **")
          } else { 
            def chkBreak(lst: List[(Action, Agent)]): Boolean = {
                lst match {
                case List() => false 
                case (((a,_)::t)) => {

                  if (breaks.contains(a)) {
                    println ("** Simulation terminated: action " +
                        a.mkstr + " is possible. **")
                        true
                  } else {
                    chkBreak(t)
                  }
                }
                }
            }
            val numTrans  = state.transitions.length
                execTrans(1+(rand.nextInt(numTrans)),state.transitions)

            val newTranses = state.transitions

            if (n==1) { 
              if (orign > 1) {
                println("Simulation complete.")
              } else {
                Unit
              }
            } else if (chkBreak(newTranses)) {
              Unit
            } else { 
              random(orign,n-1,newTranses)

            }
          }
    }
    }
  }




  /**
   * Displays an enumerated list of choice for possible transitions if any or
   * reports a deadlocked state otherwise.
   * @param tn (List of transitions, current choice number)
   */
  def menu(tn: (List[(Action, Agent)], Int)) = {

    def pt(gen0: (List[(Action, Agent)], Int)): String = {
      gen0 match {
      case (List(),1) => {
        "** Deadlocked. **"
      } case ((List(),n)) => {
        ""
      }  case ((actgent::t,n)) => {
        val a = actgent._1
            val A = actgent._2
            (if (n>9) "     " else "    ")
            + ( n) + ": --- " + (a.mkstr) + " ---> " +
            (A.agentString) + "\n" + (pt(t,n+1))
      }
      }
    }
    println("Simulated agent: " + (state.agentString) +
        "\nTransitions:\n" + pt(tn))
  }

  // Current ID is the same as the list of the history.
  def currentStateId = hist.length

      /* Single step one transition */
    /**
     * Executes the choice of the given transition given a list of
     * transitions, setting the current state to the result of the transition.
     * @param gen0: (Index to be executed - indexed from 1, list of possible transitions)  
     */
    def execTrans(gen0: (Int, List[(Action, Agent)])): Unit = {
    gen0 match {
    case ((n,List())) => {
      println("** Transition does not exist. **")
    } case (1, actGent::_) => {
      val act = actGent._1
          val agent = actGent._2
          println("  --- " + (act.mkstr) + " --->")
      hist = (state,act)::(hist)
      state  = agent
      println("Setting agent " + agent.agentString)
    }

    case (n,_::t) => execTrans((n-1,t))
    }
  }
}


object SimCommands extends Commands[SimCommand] {
  def commandLine = {
      "[" + Simulation.currentStateId + "]Sim: "
  }

  val helpString = " Simulation SimCommands" +
      "\n          --------------------\n" +
      "\n          sim <agent>;        -   reset simulated agent"+
      "\n          menu;               -   print transitions" +
      "\n          <number>;           -   follow given transition" +
      "\n          random <number>;    -   carry out random simulation" +
      "\n          ;                   -   randomly choose a transition" +
      "\n          history;            -   show state history" +
      "\n          return <number>;    -   backtrack to given state" +
      "\n          break <actions>;    -   set breakpoint actions" +
      "\n          lb;                 -   list breakpoint actions" +
      "\n          db <actions>;       -   delete breakpoint actions" +
      "\n          bind <ident>;       -   bind current state to identifier" +
      "\n          help;               -   print this menu" +
      "\n          quit;               -   terminate simulation session" 

  def showAvailableCommands() {
    println(helpString)
  }

  override
  def runCommand(input: String) = {
    // Hack to allow for direct supply of state numbers to map to choose.
    val in = input.trim()
        if (in.matches("\\d+")) {
          SimChoose(in)
        } else if (in == "") {
          SimRandom("1")
        }else {
          super.runCommand(input)
        }
  }

}


object SimulationCommand extends Command {
  val name = "sim"
      val helpShort =  "sim: simulate an agent interactively"

      val helpDetailed = 
      /* CWBDOC: sim */
      /* Automatically generated section */
      """sim A;
      |
      |
      |  (If you are running under Solaris 2.x using X, you might like to consider
      |  using the prototype graphical simulator instead. See the CWB WWW home page
      |  for more information.)
      |
      |  The following commands are available in the simulator:
      |
      |  sim B;    Stops current simulation, deletes breakpoints, starts simulating B
      |  instead.
      |
      |  menu;     Lists the (one-step) transitions from the current state.
      |
      |  n;        Follows the transition labelled with the integer n in the menu.
      |
      |  random n; Simulates no more than n steps, choosing transitions at random.
      |  Halts if it reaches a deadlocked agent,||one which can perform an action
      |  which is on the list of breakpoints (see the break command).
      |
      |  ;         Short for random 1;
      |
      |  history;  Lists the statesdef transitions by which the current state was
      |  reached.
      |
      |  return n; Stops current simulation, simulates the agent reached at step n of
      |  the current simulation instead. (E.g. return 0; starts again at the beginning.)
      |
      |  break a,b...;
      |  Sets breakpoints on actions a, b etc. See random.
      |
      |  lb;        Lists all the breakpoints.
      |
      |  db a,b,...; Deletes a,b etc. from the list of breakpoints.
      |
      |  bind A;    Bind the current state to identifier A: if the current state is
      |  B, this is equivalent to agent A = B; at the CWB main prompt.
      |
      |  help;      Prints out a summary of these simulation commands.
      |
      |  quit;      Stops simulation, returns to the CWB main prompt.
      |
      |  """.stripMargin


      def apply(input: String) = {
    SimSim(input)
  }
}
